﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using DeviceAutomationService.Models;
using DeviceAutomationService.Actions;
using DeviceAutomationService.Results;
using System.Xml.Linq;

namespace DeviceAutomationService
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    public class AutomationService : IUIAutomationService
    {
        string conn = System.Configuration.ConfigurationManager.ConnectionStrings["HisoftSmartEntities"].ConnectionString;
        
        public List<TestPlanModel> GetTestPlans()
        {
            using (HisoftSmartEntities db = new HisoftSmartEntities(conn))
            {
                List<TestPlanModel> model = new List<TestPlanModel>();
                foreach (var testPlan in db.TestPlans.ToList())
                {
                    TestPlanModel planModel = new TestPlanModel()
                    {
                        ID = testPlan.ID,
                        Name = testPlan.Name,
                        TestCases = new List<TestCaseModel>()
                    };


                    string caseIDs = testPlan.CaseIDs;
                    foreach (var caseId in caseIDs.Split(','))
                    {
                        if (!string.IsNullOrEmpty(caseId))
                        {
                            int cid = int.Parse(caseId);
                            List<ActionBaseModel> actionModels = new List<ActionBaseModel>();
                            var testCase = db.TestCases.First(c => c.ID == cid);
                            foreach (var step in testCase.Steps.Split(';'))
                            {
                                if (string.IsNullOrEmpty(step)) continue;
                                int actionId = int.Parse(step.Split(',')[0]);
                                Action action = db.Actions.Where(a => a.ID == actionId).First();
                                ActionBaseModel actionModel = new ActionBaseModel()
                                {
                                    Name = action.Name,
                                    Params = step.Substring(actionId.ToString().Length + 1)
                                };

                                actionModels.Add(actionModel);
                            }

                            if (planModel.CurrentApplicationDefinition == null)
                            {
                                Product app= testCase.Feature.Product;
                                planModel.CurrentApplicationDefinition = new ApplicationDefinition()
                                {
                                    ApplicationId = app.AppID,
                                    ApplicationIconPath = app.Icon,
                                    ApplicationName = app.Name,
                                    ApplicationPackagePath = app.PackagePath
                                };
                            }

                            planModel.TestCases.Add(new TestCaseModel() { TestCaseID = cid, TestCaseName = testCase.Name, ActionModels = actionModels });
                        }
                    }

                    model.Add(planModel);
                }

                return model;

            }
        }

        public string CreateTestRun(int planID, DeviceModel deviceModel, string executor)
        {
            using (HisoftSmartEntities db = new HisoftSmartEntities(conn))
            {
                Device device = new Device()
                {
                    GUID = deviceModel.GUID ?? Guid.NewGuid().ToString(),
                    CPU = deviceModel.CPU,
                    IP = deviceModel.IP,
                    Name = deviceModel.Name,
                    OS = deviceModel.OS,
                    OSVersion = deviceModel.OSVersion,
                    Screen = deviceModel.Screen,
                    Memery = deviceModel.Memery
                };

                // add device if new
                if (!db.Devices.Any(d => d.GUID == device.GUID))
                {
                    db.Devices.AddObject(device);
                    db.SaveChanges();
                }

                StringBuilder caseRunStates = new StringBuilder();
                foreach (var testCaseID in db.TestPlans.First(p => p.ID == planID).CaseIDs.Split(';'))
                {
                    if (!string.IsNullOrEmpty(testCaseID))
                    {
                        caseRunStates.Append(string.Format("{0},0;", testCaseID));
                    }
                }

                TestRun testRun = new TestRun()
                {
                    DeviceGUID = device.GUID,
                    TestPlanID = planID,
                    RunTime = DateTime.UtcNow,
                    Executor = executor,
                    State = "Running",
                    CaseRunStates = caseRunStates.ToString()
                };

                db.TestRuns.AddObject(testRun);
                db.SaveChanges();

                return device.GUID;
            }
        }

        public void UpdateDevices(string agentIP, List<DeviceModel> devices)
        {
            using (HisoftSmartEntities db = new HisoftSmartEntities(conn))
            {
                // make all associated devices to be offline
                db.Devices.Where(d=>d.IP== agentIP).ToList().ForEach((d) => {
                    d.State = "Offline";
                });

                foreach (var d in devices)
                {
                    if (db.Devices.Any(dv => dv.GUID == d.GUID))
                    {
                        // if device already registered, make its state as online
                        db.Devices.First(dv => dv.GUID == d.GUID).State = "Online";

                        // change the device ip to current agent ip
                        db.Devices.First(dv => dv.GUID == d.GUID).IP = agentIP;
                    }
                    else
                    {
                        // else register this device
                        Device device = new Device()
                                {
                                    GUID = d.GUID,
                                    OS = d.OS,
                                    Name = d.Name,
                                    Screen = d.Screen,
                                    Memery = d.Memery,
                                    IP = agentIP,
                                    CPU = d.CPU,
                                    OSVersion = d.OSVersion, 
                                    State="Online"
                                };
                        db.Devices.AddObject(device);
                    }
                }

                db.SaveChanges();
            }
        }

        public TestRunStatusModel GetTestRunStatus(string deviceGUID)
        {
            using (HisoftSmartEntities db = new HisoftSmartEntities(conn))
            {
                TestRunStatusModel model = new TestRunStatusModel() { AgentCommand = "NullCommand", CaseStates = new List<TestCaseStatusModel>() };
                if (!db.TestRuns.Any(r => r.DeviceGUID == deviceGUID && r.State != "Done"))
                {
                    return model;
                }

                Product app;
                model.AgentCommand = "Refresh";

                // if there is no case running, get first one pending and make it running
                if (!db.TestRuns.Any(r => r.DeviceGUID == deviceGUID && r.State == "Running"))
                {
                    var testRunPending = db.TestRuns.First(r => r.State == "Pending");
                    testRunPending.State = "Running";
                    app = testRunPending.TestPlan.Product;
                    model.ApplicationDefinition = new ApplicationDefinition()
                    {
                        ApplicationId = app.AppID,
                        ApplicationIconPath = app.Icon,
                        ApplicationName = app.Name,
                        ApplicationPackagePath = app.PackagePath
                    };

                    model.AgentCommand = "Start";
                    model.AgentCommand += string.Format(" Application {0} [{1}]", app.Name, app.AppID.ToString());
                    db.SaveChanges();
                    return model;
                }

                // else get current running case
                var testRun = db.TestRuns.First(r => r.DeviceGUID == deviceGUID && r.State == "Running");
                app = testRun.TestPlan.Product;
                model.ApplicationDefinition = new ApplicationDefinition()
                {
                    ApplicationId = app.AppID,
                    ApplicationIconPath = app.Icon,
                    ApplicationName = app.Name,
                    ApplicationPackagePath = app.PackagePath
                };
                

                // get first test run that is not finished
                if (testRun.CaseRunStates.Contains(",0"))
                {
                    #region if test run is not finished, restart app when case executed or wait for it
                    string previous = "0,2";
                    foreach (string caseState in testRun.CaseRunStates.Split(';'))
                    {
                        if (string.IsNullOrEmpty(caseState)) break;
                        if (caseState.Contains('0'))
                        {
                            // current case is pending, so previous case is 
                            // 1: running, 2: pass, 3: failed
                            // the agent client should wait / stop and start app if there is still cases pending
                            switch (previous.Last())
                            {
                                case '1':
                                    model.AgentCommand = string.Format("Waiting for test case {0} running", previous.Split(',')[0]);
                                    break;
                                case '2': // pass
                                case '3': // failed
                                    model.AgentCommand = previous == "0,2" ? "Start" : "StopAndStart";
                                    model.AgentCommand += string.Format(" Application {0} [{1}]", app.Name, app.AppID.ToString());

                                    // make current test case to be running
                                    string newCaseState = caseState.Replace(",0", ",1");
                                    testRun.CaseRunStates = testRun.CaseRunStates.Replace(caseState, newCaseState);

                                    break;
                                default:
                                    model.AgentCommand = "State Exception : " + previous.Last();
                                    break;
                            }

                            break;
                        }

                        previous = caseState;
                    }
                    #endregion
                }
                else if (testRun.CaseRunStates.Contains(",1"))
                {
                    // last case is stil running, wait for its state to be 2 / 3

                }
                else
                {
                    #region all cases executed, finish the test run, and stop the app
                    testRun.State = "Done";
                    db.SaveChanges();
                    model.AgentCommand = "Stop";
                    #endregion
                }

                model.CaseStates = CaseStatesFromStr(testRun.CaseRunStates, db);

                

                db.SaveChanges();
                return model;
            }
        }

        List<TestCaseStatusModel> CaseStatesFromStr(string caseStates, HisoftSmartEntities db)
        {
            List<TestCaseStatusModel> states = new List<TestCaseStatusModel>();
            foreach (string caseState in caseStates.Split(';'))
            {
                if (!string.IsNullOrEmpty(caseState))
                {
                    TestCaseStatusModel caseStatus = new TestCaseStatusModel();
                    int caseId = int.Parse(caseState.Split(',')[0]);
                    caseStatus.TestCaseID = caseId;
                    caseStatus.TestCaseName = db.TestCases.First(c => c.ID == caseId).Name;
                    caseStatus.State = caseState.Split(',')[1];
                    states.Add(caseStatus);
                }
            }

            return states;
        }

        public TestCaseModel GetTestCase(string deviceGUID)
        {
            using (HisoftSmartEntities db = new HisoftSmartEntities(conn))
            {
                TestCaseModel model = new TestCaseModel();
                model.ActionModels = new List<ActionBaseModel>();
                if (!db.TestRuns.Any(r => r.DeviceGUID == deviceGUID && r.State == "Running"))
                {
                    return null;
                }

                var run = db.TestRuns.First(r => r.DeviceGUID == deviceGUID && r.State == "Running");
                
                List<TestCaseStatusModel> caseStatusModels = CaseStatesFromStr(run.CaseRunStates, db);
                if(!caseStatusModels.Any(m => m.State == "1"))
                {
                    return null;
                }

                TestCaseStatusModel caseStatusModel = caseStatusModels.First(t => t.State == "1");
                var testCase = db.TestCases.First(c => c.ID == caseStatusModel.TestCaseID);
                foreach (var step in testCase.Steps.Split(';'))
                {
                    if (string.IsNullOrEmpty(step)) continue;
                    int actionId = int.Parse(step.Split(',')[0]);
                    Action action = db.Actions.Where(a => a.ID == actionId).First();
                    ActionBaseModel actionModel = new ActionBaseModel()
                    {
                        ID = action.ID,
                        Name = action.Name,
                        Params = step.Substring(actionId.ToString().Length + 1)
                    };

                    model.ActionModels.Add(actionModel);
                }

                model.TestCaseID = testCase.ID;
                model.TestCaseName = testCase.Name;
                model.TestPlanID = run.TestPlan.ID;
                model.TestRunID = run.ID;
                return model;
            }

        }

        public void SubmitTestCaseResult(TestCaseResultModel result)
        {
            using (HisoftSmartEntities db = new HisoftSmartEntities(conn))
            {
                TestCaseResult tcResult = new TestCaseResult()
                {
                    CompleteTime = DateTime.Now,
                    TestRunID = result.TestRunID,
                    TestPlanID = result.TestPlanID,
                    TestCaseID = result.TestCaseID, 
                    ActionResults = TestCaseResultToLog(result),
                    Result = result.Result
                };

                db.TestCaseResults.AddObject(tcResult);

                // update test case status in test run
                TestRun run = db.TestRuns.First(r => r.ID == result.TestRunID);
                run.CaseRunStates = run.CaseRunStates.Replace(
                    string.Format("{0},1;", tcResult.TestCaseID),
                    string.Format("{0},{1};", tcResult.TestCaseID,
                    tcResult.Result == "Pass" ? "2" : "3"));
                db.SaveChanges();
            }
        }

        private string TestCaseResultToLog(TestCaseResultModel result)
        {
            StringBuilder log = new StringBuilder();
            XDocument xDoc = new XDocument();
            xDoc.Add(new XElement("Root"));
            XElement testCaseEle = new XElement("TestCaseRun");
            testCaseEle.SetAttributeValue("TestRunID", result.TestRunID);
            testCaseEle.SetAttributeValue("Result", result.Result);

            foreach (var actionResult in result.ActionResults)
            {
                XElement testActionEle = new XElement("TestAction");
                testActionEle.SetAttributeValue("ID", actionResult.ActionBaseModel.ID);
                testActionEle.SetAttributeValue("Name", actionResult.ActionBaseModel.Name);
                testActionEle.SetAttributeValue("Params", actionResult.ActionBaseModel.Params);
                testActionEle.SetAttributeValue("Result", actionResult.Result);
                testCaseEle.Add(testActionEle);
            }

            xDoc.Root.Add(testCaseEle);
            return xDoc.ToString();
        }


    }
}
